﻿namespace LogManager.Utils.Xml
{
    using LogManager.Utils.Helpers;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Xml.Linq;

    public class XmlResolver<TBaseElement>
    {
        public XElement BaseNode { get; private set; }

        public XmlResolver(XElement baseNode)
        {
            this.BaseNode = baseNode;
        }

        public XElement Child<TOut>(Expression<Func<TBaseElement, TOut>> propertyExp)
        {
            return Children(propertyExp).SingleOrDefault();
        }

        public IEnumerable<XElement> Children<TOut>(Expression<Func<TBaseElement, TOut>> propertyExp)
        {
            return BaseNode.Children<TBaseElement, TOut>(propertyExp);
        }

        public TOut GetAttributeValue<TOut>(Expression<Func<TBaseElement, TOut>> propertyExp)
        {
            return BaseNode.GetAttributeValue<TBaseElement, TOut>(propertyExp);
        }

        public IEnumerable<TChild> ResolveChildren<TChild>(Expression<Func<TBaseElement, IEnumerable<TChild>>> propertyExp, Func<XElement, TChild> CreateChild)
        {
            foreach (var node in BaseNode.Children(propertyExp))
            {
                yield return CreateChild(node);
            }
        }

        public TChild ResolveChild<TChild>(Expression<Func<TBaseElement, TChild>> propertyExp, Func<XElement, TChild> CreateChild)
        {
            return CreateChild(Child(propertyExp));
        }

        public void SetAttributeValue<TOut>(Expression<Func<TBaseElement, TOut>> propertyExp, TOut objectValue)
        {
            BaseNode.SetAttributeValue<TBaseElement, TOut>(propertyExp, objectValue);
        }

        public void SetChild<TChild>(Expression<Func<TBaseElement, TChild>> propertyExp, XElement node)
        {
            var child = Child(propertyExp);
            if (child != null)
                child.ReplaceWith(node);
        }

        public void SetChildren<TChild>(Expression<Func<TBaseElement, IEnumerable<TChild>>> propertyExp, IEnumerable<XElement> nodes)
        {
            Children(propertyExp).Remove();

            if (nodes != null)
                nodes.ForEach(node => BaseNode.Add(node));
        }
    }

    public static class XmlResolver
    {
        public static IEnumerable<XElement> Children<Tin, TOut>(this XElement baseElement, Expression<Func<Tin, TOut>> propertyExp)
        {
            var expression = (MemberExpression)propertyExp.Body;
            var propertyName = expression.Member.Name;

            if (propertyName.EndsWith("s") && IsPlural(expression.Type))
            {
                propertyName = propertyName.Remove(propertyName.Length - 1, 1);
            }

            return baseElement.Descendants().Where(p => p.Name.LocalName == propertyName);
        }

        public static Tout GetAttributeValue<Tin, Tout>(this XElement baseElement, Expression<Func<Tin, Tout>> propertyExp)
        {
            var expression = (MemberExpression)propertyExp.Body;
            var propertyName = expression.Member.Name;

            var attributeValue = baseElement.Attributes().Single(p => p.Name.LocalName == propertyName).Value;
            if (expression.Type.IsEnum)
            {
                return (Tout)Enum.Parse(typeof(Tout), attributeValue);
            }
            else
            {
                return (Tout)Convert.ChangeType(attributeValue, typeof(Tout));
            }
        }

        public static void SetAttributeValue<Tin, Tout>(this XElement baseElement, Expression<Func<Tin, Tout>> propertyExp, Tout objectValue)
        {
            var expression = (MemberExpression)propertyExp.Body;
            var propertyName = expression.Member.Name;

            // Ensure attribute is created
            baseElement.SetAttributeValue(propertyName, "");
            var attribute = baseElement.Attributes().Single(p => p.Name.LocalName == propertyName);

            var value = (string)Convert.ChangeType(objectValue, typeof(string));
            if (typeof(Tout) == typeof(bool))
                attribute.Value = value.ToLower();
            else
                attribute.Value = value;
        }

        private static bool IsPlural(Type type)
        {
            if (type == typeof(string))
                return false;

            if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
                return true;

            foreach (Type intType in type.GetInterfaces())
            {
                if (intType.IsGenericType
                    && intType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
                {
                    return true;
                }
            }
            return false; ;
        }
    }
}
